// Netris. A free, open source Tetris(tm) clone
// Copyright (C) 2005  Parez Timothy
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

using System;
using System.Text;
using System.Drawing;
//using System.Drawing.Drawing2D;
using SdlDotNet;
using SdlDotNet.Sprites;

namespace Beaglebits.Games.Netris
{
    public class Block
    {
    	
        private Point position;
        private Bitmap squareImage;					//Image to use for the squares in this block
        private Square[] squares = new Square[4];
        private Surface drawingSurface;				//Surface on which to draw the block
        
        private BlockTypeEnum blockType;
        private RotationStatusEnum rotationStatus = RotationStatusEnum.North;

        public Point Position
        {
            get
            {
                return this.position;
            }
            set
            {
                this.position = value;
            }
        }

        public int SquareSize
        {
            get
            {
                return this.squareImage.Width;
            }
        }

        public Bitmap SquareImage
        {
            get
            {
                return this.squareImage;
            }
            set
            {
                this.squareImage = value;
            }
        }

        public int Top
        {
            get
            {
                int min = int.MaxValue;
                foreach(Square square in this.squares)
                {
                	min = (square.Position.Y < min ? square.Position.Y : min);
                }

                return min;
            }
        }

        private Square[] Squares
        {
            get
            {
                return this.squares;
            }
            set
            {
                if (value.Length != 4)
                {
                    throw new ArgumentException("The array must be 4 elements long", "Length");
                }
                this.squares = value;
            }
        }

        public BlockTypeEnum BlockType
        {
            get
            {
                return this.blockType;
            }
            set
            {
                this.blockType = value;
            }
        }

        public RotationStatusEnum RotationStatus
        {
            get
            {
                return this.rotationStatus;
            }
            set
            {
                this.rotationStatus = value;
            }
        }

        public Block(Point Position, Surface DrawingSurface) : this(Position, BlockTypeEnum.Undefined, DrawingSurface)
        {
            //Calls the Block(Point, BlockTypeEnum) constructor
        }

        public Block(Point Position, BlockTypeEnum BlockType, Surface DrawingSurface)
        {
            this.position = Position;
            this.drawingSurface = DrawingSurface;
                	
            if (BlockType == BlockTypeEnum.Undefined)
            {
                Random rnd = new Random();
                this.blockType = (BlockTypeEnum)rnd.Next(1, 8);

            }
            else
            {
                this.blockType = BlockType;
            }
            
            //Load the correct image according to the BlockType
            //The images are provided by the GameField class
            this.squareImage = GameField.SquareImages[(int)this.blockType - 1];

            for (int i = 0;i < this.squares.Length;i++)
            {
            	this.squares[i] = new Square(new Surface(this.squareImage));                
            }

            //Draw the blocks, note that the order in which we draw the squares is important to the Rotate method, in order to define the fixed square
            switch (this.blockType)
            {
                case BlockTypeEnum.Square:
                    this.squares[0].Position = new Point(this.position.X, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X + this.SquareSize, this.position.Y);
                    this.squares[2].Position = new Point(this.position.X, this.position.Y + this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X + this.SquareSize, this.position.Y + this.SquareSize);
                    break;

                case BlockTypeEnum.Line:
                    this.squares[0].Position = new Point(this.position.X, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X, this.position.Y + this.SquareSize);
                    this.squares[2].Position = new Point(this.position.X, this.position.Y + 2 * this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X, this.position.Y + 3 * this.SquareSize);
                    break;

                case BlockTypeEnum.J:
                    this.squares[0].Position = new Point(this.position.X + this.SquareSize, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X + this.SquareSize, this.position.Y + this.SquareSize);
                    this.squares[2].Position = new Point(this.position.X + this.SquareSize, this.position.Y + 2 * this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X, this.position.Y + 2 * this.SquareSize);
                    break;

                case BlockTypeEnum.L:
                    this.squares[0].Position = new Point(this.position.X, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X, this.position.Y + this.SquareSize);
                    this.squares[2].Position = new Point(this.position.X, this.position.Y + 2 * this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X + this.SquareSize, this.position.Y + 2 * this.SquareSize);
                    break;

                case BlockTypeEnum.T:
                    this.squares[0].Position = new Point(this.position.X, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X + this.SquareSize, this.position.Y);
                    this.squares[2].Position = new Point(this.position.X + 2 * this.SquareSize, this.position.Y);
                    this.squares[3].Position = new Point(this.position.X + this.SquareSize, this.position.Y + this.SquareSize);
                    break;

                case BlockTypeEnum.S:
                    this.squares[0].Position = new Point(this.position.X + this.SquareSize, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X + 2 * this.SquareSize, this.position.Y);
                    this.squares[2].Position = new Point(this.position.X, this.position.Y + this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X + this.SquareSize, this.position.Y + this.SquareSize);
                    break;

                case BlockTypeEnum.Z:
                    this.squares[0].Position = new Point(this.position.X, this.position.Y);
                    this.squares[1].Position = new Point(this.position.X + this.SquareSize, this.position.Y);
                    this.squares[2].Position = new Point(this.position.X + this.SquareSize, this.position.Y + this.SquareSize);
                    this.squares[3].Position = new Point(this.position.X + 2 * this.SquareSize, this.position.Y + this.SquareSize);
                    break;
            }
        }

        public bool Down()
        {
            //Check if there is no block under the current one 
            //The calculation is the position in the array, Y + 1 because we're going down
            if (GameField.IsEmpty(this.squares[0].Position.X / this.SquareSize, this.squares[0].Position.Y / this.SquareSize + 1) &&
                GameField.IsEmpty(this.squares[1].Position.X / this.SquareSize, this.squares[1].Position.Y / this.SquareSize + 1) &&
                GameField.IsEmpty(this.squares[2].Position.X / this.SquareSize, this.squares[2].Position.Y / this.SquareSize + 1) &&
                GameField.IsEmpty(this.squares[3].Position.X / this.SquareSize, this.squares[3].Position.Y / this.SquareSize + 1))
            {
                //Hide the block first
                this.Hide();

                //Update the block
                foreach (Square square in this.squares)
                {
                    square.Position = new Point(square.Position.X, square.Position.Y + this.SquareSize);
                }

                //Draw the block in the new position
                this.Show();

                //Return true
                return true;
            }
            else
            {
                //Don't go down, there's a block bellow the current one
                foreach (Square square in this.squares)
                {
                    GameField.StopSquare(square, square.Position.X / SquareSize, square.Position.Y / SquareSize);
                }

                //Return false
                return false;
            }
        }

        public bool Right()
        {
            //Check if there's a block to the right of this one
            //The calculation is the position in the array, X + 1 because we are moving right
            if (GameField.IsEmpty(this.squares[0].Position.X / this.SquareSize + 1, this.squares[0].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[1].Position.X / this.SquareSize + 1, this.squares[1].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[2].Position.X / this.SquareSize + 1, this.squares[2].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[3].Position.X / this.SquareSize + 1, this.squares[3].Position.Y / this.SquareSize))
            {
                //Hide the block first
                this.Hide();

                //Update the block
                foreach (Square square in this.squares)
                {
                    square.Position = new Point(square.Position.X + this.SquareSize, square.Position.Y);
                }

                //Draw the block in the new position
                this.Show();

                //Return true
                return true;
            }
            else
            {
                return false; //Unlike the Down() method, this method doesn't stop the square
            }
        }

        public bool Left()
        {
             //Check if there's a block to the left of this one
            //The calculation is the position in the array, X - 1 because we are moving left
            if (GameField.IsEmpty(this.squares[0].Position.X / this.SquareSize - 1, this.squares[0].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[1].Position.X / this.SquareSize - 1, this.squares[1].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[2].Position.X / this.SquareSize - 1, this.squares[2].Position.Y / this.SquareSize) &&
                GameField.IsEmpty(this.squares[3].Position.X / this.SquareSize - 1, this.squares[3].Position.Y / this.SquareSize))
            {
                //Hide the block first
                this.Hide();

                //Update the block
                foreach (Square square in this.squares)
                {
                    square.Position = new Point(square.Position.X - this.SquareSize, square.Position.Y);
                }

                //Draw the block in the new position
                this.Show();

                //Return true
                return true;
            }
            else
            {
                return false; //Unlike the Down() method, this method doesn't stop the square
            }
        }

        public void Show()
        {
            foreach (Square square in this.squares)
            {
                square.Show(this.drawingSurface);
            }
			
            //TODO: Update a specific region of the screen only
            //Flip after the entire block is drawn
            GameField.GameFieldSurface.Flip();
        }

        public void Hide()
        {
            foreach (Square square in this.squares)
            {
                square.Hide(this.drawingSurface);
            }
            
           	//Flip after the entire block is hidden
            GameField.GameFieldSurface.Flip();
        }

        public void Rotate()
        {
            /* Instead or rotating the blocks twice, once for testing and once if there are no collisions
               we can store the current position of the block, rotate it and if a colision is detected
               redraw the block with the old position */
            
            //Store the current position of the squares (copy the positions, the blocks are reference types)
            Point[] oldPositions = new Point[4];
            oldPositions[0] = this.squares[0].Position;
            oldPositions[1] = this.squares[1].Position;
            oldPositions[2] = this.squares[2].Position;
            oldPositions[3] = this.squares[3].Position;

            //Store the current rotation status of the block
            RotationStatusEnum oldRotationStatus = this.rotationStatus;

            //Hide this block
            this.Hide();

            //Rotate the blocks (check later)
            switch (this.blockType)
            {
                case BlockTypeEnum.Square:
                    //Don't do anything, these blocks don't rotate
                    break;

                case BlockTypeEnum.Line:
                    //Rotate around the second square
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[1].Position.X - this.SquareSize, this.squares[1].Position.Y);
                            this.squares[2].Position = new Point(this.squares[1].Position.X + this.SquareSize, this.squares[1].Position.Y);
                            this.squares[3].Position = new Point(this.squares[1].Position.X + 2 * this.SquareSize, this.squares[1].Position.Y);
                            break;
                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y - this.SquareSize);
                            this.squares[2].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y + this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y + 2 * this.SquareSize );
                            break;
                    }
                    break;

                case BlockTypeEnum.J:
                    //Rotate around the third square (Draw this on paper to see why)
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[2].Position.X + 2 * this.SquareSize, this.squares[2].Position.Y);
                            this.squares[1].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            this.squares[3].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - this.SquareSize);
                            break;
                       
                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.South;
                            this.squares[0].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + 2 * this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            break;

                        case RotationStatusEnum.South:
                            this.rotationStatus = RotationStatusEnum.West;
                            this.squares[0].Position = new Point(this.squares[2].Position.X - 2 * this.SquareSize, this.squares[2].Position.Y);
                            this.squares[1].Position = new Point(this.squares[2].Position.X - this.SquareSize, this.squares[2].Position.Y);
                            this.squares[3].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + this.SquareSize);
                            break;

                        case RotationStatusEnum.West:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - 2 * this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[2].Position.X - this.SquareSize, this.squares[2].Position.Y);
                            break;
                    }
                    break;

                case BlockTypeEnum.L:
                    //Rotate around the third block (Draw this on paper to see why)
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[2].Position.X + 2 * this.SquareSize, this.squares[2].Position.Y);
                            this.squares[1].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            this.squares[3].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + this.SquareSize);
                            break;

                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.South;
                            this.squares[0].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + 2 * this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[2].Position.X - this.SquareSize, this.squares[2].Position.Y);
                            break;

                        case RotationStatusEnum.South:
                            this.rotationStatus = RotationStatusEnum.West;
                            this.squares[0].Position = new Point(this.squares[2].Position.X - 2 * this.SquareSize, this.squares[2].Position.Y);
                            this.squares[1].Position = new Point(this.squares[2].Position.X - this.SquareSize, this.squares[2].Position.Y);
                            this.squares[3].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - this.SquareSize);
                            break;

                        case RotationStatusEnum.West:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - 2 * this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            break;
                    }
                    break;

                case BlockTypeEnum.T:
                    //Rotate around the second square (Draw this on paper to see why)
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y - this.SquareSize);
                            this.squares[2].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y + this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[1].Position.X - this.SquareSize, this.squares[1].Position.Y);
                            break;

                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.South;
                            this.squares[0].Position = new Point(this.squares[1].Position.X + this.SquareSize, this.squares[1].Position.Y);
                            this.squares[2].Position = new Point(this.squares[1].Position.X - this.SquareSize, this.squares[1].Position.Y);
                            this.squares[3].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y - this.SquareSize);
                            break;

                        case RotationStatusEnum.South:
                            this.rotationStatus = RotationStatusEnum.West;
                            this.squares[0].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y + this.SquareSize);
                            this.squares[2].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y - this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[1].Position.X + this.SquareSize, this.squares[1].Position.Y);
                            break;

                        case RotationStatusEnum.West:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[1].Position.X - this.SquareSize, this.squares[1].Position.Y);
                            this.squares[2].Position = new Point(this.squares[1].Position.X + this.SquareSize, this.squares[1].Position.Y);
                            this.squares[3].Position = new Point(this.squares[1].Position.X, this.squares[1].Position.Y + this.SquareSize);
                            break;
                    }
                    break;

                case BlockTypeEnum.Z:
                    //Rotate around the third square (Draw this on paper to see why)
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y - this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            this.squares[3].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y + this.SquareSize);
                            break;
                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[2].Position.X - this.SquareSize, this.squares[2].Position.Y - this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[2].Position.X, this.squares[2].Position.Y - this.SquareSize);
                            this.squares[3].Position = new Point(this.squares[2].Position.X + this.SquareSize, this.squares[2].Position.Y);
                            break;
                    }
                    break;

                case BlockTypeEnum.S:
                    //Rotate around the fourth square (Draw this on paper to see why)
                    switch (this.rotationStatus)
                    {
                        case RotationStatusEnum.North:
                            this.rotationStatus = RotationStatusEnum.East;
                            this.squares[0].Position = new Point(this.squares[3].Position.X + this.SquareSize, this.squares[3].Position.Y);
                            this.squares[1].Position = new Point(this.squares[3].Position.X + this.SquareSize, this.squares[3].Position.Y + this.SquareSize);
                            this.squares[2].Position = new Point(this.squares[3].Position.X, this.squares[3].Position.Y - this.SquareSize);
                            break;

                        case RotationStatusEnum.East:
                            this.rotationStatus = RotationStatusEnum.North;
                            this.squares[0].Position = new Point(this.squares[3].Position.X, this.squares[3].Position.Y - this.SquareSize);
                            this.squares[1].Position = new Point(this.squares[3].Position.X + this.SquareSize, this.squares[3].Position.Y - this.SquareSize);
                            this.squares[2].Position = new Point(this.squares[3].Position.X - this.SquareSize, this.squares[3].Position.Y);
                            break;
                    }
                    break;
            }

            //Now test of a the rotated block overlaps another one
            bool isEmpty = !(GameField.IsEmpty(this.squares[0].Position.X / this.SquareSize, this.squares[0].Position.Y / this.SquareSize) &&
                  GameField.IsEmpty(this.squares[1].Position.X / this.SquareSize, this.squares[1].Position.Y / this.SquareSize) &&
                  GameField.IsEmpty(this.squares[2].Position.X / this.SquareSize, this.squares[2].Position.Y / this.SquareSize) &&
                  GameField.IsEmpty(this.squares[3].Position.X / this.SquareSize, this.squares[3].Position.Y / this.SquareSize));

            if (isEmpty)
            {
                //Draw the block at its original position
                this.rotationStatus = oldRotationStatus;
                this.squares[0].Position = oldPositions[0];
                this.squares[1].Position = oldPositions[1];
                this.squares[2].Position = oldPositions[2];
                this.squares[3].Position = oldPositions[3];
            }

            //Now draw the block at the correct position
            this.Show();
        }
    }
}
